﻿/*! JsRender v1.0pre - (jsrender.js version: does not require jQuery): http://github.com/BorisMoore/jsrender */
/*
* Optimized version of jQuery Templates, fosr rendering to string, using 'codeless' markup.
*
* Copyright 2011, Boris Moore
* Released under the MIT License.
*/
window.JsViews || window.jQuery && jQuery.views || (function (window, undefined) {

    var $, _$, JsViews, viewsNs, tmplEncode, render, rTag, registerTags, registerHelpers, extend,
    FALSE = false, TRUE = true,
    jQuery = window.jQuery, document = window.document,
    htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
    rPath = /^(true|false|null|[\d\.]+)|(\w+|\$(view|data|ctx|(\w+)))([\w\.]*)|((['"])(?:\\\1|.)*\7)$/g,
    rParams = /(\$?[\w\.\[\]]+)(?:(\()|\s*(===|!==|==|!=|<|>|<=|>=)\s*|\s*(\=)\s*)?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g,
    rNewLine = /\r?\n/g,
    rUnescapeQuotes = /\\(['"])/g,
    rEscapeQuotes = /\\?(['"])/g,
    rBuildHash = /\x08([^\x08]+)\x08/g,
    autoName = 0,
    escapeMapForHtml = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;"
    },
    htmlSpecialChar = /[\x00"&'<>]/g,
    slice = Array.prototype.slice;

    if (jQuery) {

        ////////////////////////////////////////////////////////////////////////////////////////////////
        // jQuery is loaded, so make $ the jQuery object
        $ = jQuery;

        $.fn.extend({
            // Use first wrapped element as template markup.
            // Return string obtained by rendering the template against data.
            render: function (data, context, parentView, path) {
                return render(data, this[0], context, parentView, path);
            },

            // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
            template: function (name, context) {
                return $.template(name, this[0], context);
            }
        });

    } else {

        ////////////////////////////////////////////////////////////////////////////////////////////////
        // jQuery is not loaded. Make $ the JsViews object

        // Map over the $ in case of overwrite
        _$ = window.$;

        window.JsViews = JsViews = window.$ = $ = {
            extend: function (target, source) {
                var name;
                for (name in source) {
                    target[name] = source[name];
                }
                return target;
            },
            isArray: Array.isArray || function (obj) {
                return Object.prototype.toString.call(obj) === "[object Array]";
            },
            noConflict: function () {
                if (window.$ === JsViews) {
                    window.$ = _$;
                }
                return JsViews;
            }
        };
    }

    extend = $.extend;

    //=================
    // View constructor
    //=================

    function View(context, path, parentView, data, template) {
        // Returns a view data structure for a new rendered instance of a template.
        // The content field is a hierarchical array of strings and nested views.

        parentView = parentView || { viewsCount: 0, ctx: viewsNs.helpers };

        var parentContext = parentView && parentView.ctx;

        return {
            jsViews: "v1.0pre",
            path: path || "",
            // inherit context from parentView, merged with new context.
            itemNumber: ++parentView.viewsCount || 1,
            viewsCount: 0,
            tmpl: template,
            data: data || parentView.data || {},
            // Set additional context on this view (which will modify the context inherited from the parent, and be inherited by child views)
            ctx: context && context === parentContext
            ? parentContext
            : (parentContext ? extend(extend({}, parentContext), context) : context || {}),
            // If no jQuery, extend does not support chained copies - so limit to two parameters
            parent: parentView
        };
    }
    extend($, {
        views: viewsNs = {
            templates: {},
            tags: {
                "if": function () {
                    var ifTag = this,
                    view = ifTag._view;
                    view.onElse = function (presenter, args) {
                        var i = 0,
                        l = args.length;
                        while (l && !args[i++]) {
                            // Only render content if args.length === 0 (i.e. this is an else with no condition) or if a condition argument is truey
                            if (i === l) {
                                return "";
                            }
                        }
                        view.onElse = undefined; // If condition satisfied, so won't run 'else'.
                        return render(view.data, presenter.tmpl, view.ctx, view);
                    };
                    return view.onElse(this, arguments);
                },
                "else": function () {
                    var view = this._view;
                    return view.onElse ? view.onElse(this, arguments) : "";
                },
                each: function () {
                    var i,
                    self = this,
                    result = "",
                    args = arguments,
                    l = args.length,
                    content = self.tmpl,
                    view = self._view;
                    for (i = 0; i < l; i++) {
                        result += args[i] ? render(args[i], content, self.ctx || view.ctx, view, self._path, self._ctor) : "";
                    }
                    return l ? result
                    // If no data parameter, use the current $data from view, and render once
                    : result + render(view.data, content, view.ctx, view, self._path, self.tag);
                },
                "=": function (value) {
                    return value;
                },
                "*": function (value) {
                    return value;
                }
            },
            helpers: {
                not: function (value) {
                    return !value;
                }
            },
            allowCode: FALSE,
            debugMode: TRUE,
            err: function (e) {
                return viewsNs.debugMode ? ("<br/><b>Error:</b> <em> " + (e.message || e) + ". </em>") : '""';
            },

            //===============
            // setDelimiters
            //===============

            setDelimiters: function (openTag, closeTag) {
                // Set or modify the delimiter characters for tags: "{{" and "}}"
                var firstCloseChar = closeTag.charAt(0),
                secondCloseChar = closeTag.charAt(1);
                openTag = "\\" + openTag.charAt(0) + "\\" + openTag.charAt(1);
                closeTag = "\\" + firstCloseChar + "\\" + secondCloseChar;

                // Build regex with new delimiters
                //           {{
                rTag = openTag
                //       #      tag    (followed by space,! or })             or equals or  code
                + "(?:(?:(\\#)?(\\w+(?=[!\\s\\" + firstCloseChar + "]))" + "|(?:(\\=)|(\\*)))"
                //     params
                + "\\s*((?:[^\\" + firstCloseChar + "]|\\" + firstCloseChar + "(?!\\" + secondCloseChar + "))*?)"
                //   encoding
                + "(!(\\w*))?"
                //        closeBlock
                + "|(?:\\/([\\w\\$\\.\\[\\]]+)))"
                //  }}
            + closeTag;

                // Default rTag:     #    tag              equals code        params         encoding    closeBlock
                //      /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;

                rTag = new RegExp(rTag, "g");
            },


            //===============
            // registerTags
            //===============

            // Register declarative tag.
            registerTags: registerTags = function (name, tagFn) {
                var key;
                if (typeof name === "object") {
                    for (key in name) {
                        registerTags(key, name[key]);
                    }
                } else {
                    // Simple single property case.
                    viewsNs.tags[name] = tagFn;
                }
                return this;
            },

            //===============
            // registerHelpers
            //===============

            // Register helper function for use in markup.
            registerHelpers: registerHelpers = function (name, helper) {
                if (typeof name === "object") {
                    // Object representation where property name is path and property value is value.
                    // TODO: We've discussed an "objectchange" event to capture all N property updates here. See TODO note above about propertyChanges.
                    var key;
                    for (key in name) {
                        registerHelpers(key, name[key]);
                    }
                } else {
                    // Simple single property case.
                    viewsNs.helpers[name] = helper;
                }
                return this;
            },

            //===============
            // tmpl.encode
            //===============

            encode: function (encoding, text) {
                return text
                ? (tmplEncode[encoding || "html"] || tmplEncode.html)(text) // HTML encoding is the default
                : "";
            },

            encoders: tmplEncode = {
                "none": function (text) {
                    return text;
                },
                "html": function (text) {
                    // HTML encoding helper: Replace < > & and ' and " by corresponding entities.
                    // Implementation, from Mike Samuel <msamuel@google.com>
                    return String(text).replace(htmlSpecialChar, replacerForHtml);
                }
                //TODO add URL encoding, and perhaps other encoding helpers...
            },

            //===============
            // renderTag
            //===============

            renderTag: function (tag, view, encode, content, tagProperties) {
                // This is a tag call, with arguments: "tag", view, encode, content, presenter [, params...]
                var ret, ctx, name,
                args = arguments,
                presenters = viewsNs.presenters;
                hash = tagProperties._hash,
                tagFn = viewsNs.tags[tag];

                if (!tagFn) {
                    return "";
                }

                content = content && view.tmpl.nested[content - 1];
                tagProperties.tmpl = tagProperties.tmpl || content || undefined;
                // Set the tmpl property to the content of the block tag, unless set as an override property on the tag

                if (presenters && presenters[tag]) {
                    ctx = extend(extend({}, tagProperties.ctx), tagProperties);
                    delete ctx.ctx;
                    delete ctx._path;
                    delete ctx.tmpl;
                    tagProperties.ctx = ctx;
                    tagProperties._ctor = tag + (hash ? "=" + hash.slice(0, -1) : "");

                    tagProperties = extend(extend({}, tagFn), tagProperties);
                    tagFn = viewsNs.tags.each; // Use each to render the layout template against the data
                }

                tagProperties._encode = encode;
                tagProperties._view = view;
                ret = tagFn.apply(tagProperties, args.length > 5 ? slice.call(args, 5) : [view.data]);
                return ret || (ret === undefined ? "" : ret.toString()); // (If ret is the value 0 or false or null, will render to string) 
            }
        },

        //===============
        // render
        //===============

        render: render = function (data, tmpl, context, parentView, path, tagName) {
            // Render template against data as a tree of subviews (nested template), or as a string (top-level template).
            // tagName parameter for internal use only. Used for rendering templates registered as tags (which may have associated presenter objects)
            var i, l, dataItem, arrayView, content, result = "";

            if (arguments.length === 2 && data.jsViews) {
                parentView = data;
                context = parentView.ctx;
                data = parentView.data;
            }
            tmpl = $.template(tmpl);
            if (!tmpl) {
                return ""; // Could throw...
            }

            if ($.isArray(data)) {
                // Create a view item for the array, whose child views correspond to each data item.
                arrayView = new View(context, path, parentView, data);
                l = data.length;
                for (i = 0, l = data.length; i < l; i++) {
                    dataItem = data[i];
                    content = dataItem ? tmpl(dataItem, new View(context, path, arrayView, dataItem, tmpl, this)) : "";
                    result += viewsNs.activeViews ? "<!--item-->" + content + "<!--/item-->" : content;
                }
            } else {
                result += tmpl(data, new View(context, path, parentView, data, tmpl));
            }

            return viewsNs.activeViews
            // If in activeView mode, include annotations
            ? "<!--tmpl(" + (path || "") + ") " + (tagName ? "tag=" + tagName : tmpl._name) + "-->" + result + "<!--/tmpl-->"
            // else return just the string result
            : result;
        },

        //===============
        // template
        //===============

        template: function (name, tmpl) {
            // Set:
            // Use $.template( name, tmpl ) to cache a named template,
            // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc.
            // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.

            // Get:
            // Use $.template( name ) to access a cached template.
            // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
            // will return the compiled template, without adding a name reference.
            // If templateString is not a selector, $.template( templateString ) is equivalent
            // to $.template( null, templateString ). To ensure a string is treated as a template,
            // include an HTML element, an HTML comment, or a template comment tag.

            if (tmpl) {
                // Compile template and associate with name
                if ("" + tmpl === tmpl) { // type string
                    // This is an HTML string being passed directly in.
                    tmpl = compile(tmpl);
                } else if (jQuery && tmpl instanceof $) {
                    tmpl = tmpl[0];
                }
                if (tmpl) {
                    if (jQuery && tmpl.nodeType) {
                        // If this is a template block, use cached copy, or generate tmpl function and cache.
                        tmpl = $.data(tmpl, "tmpl") || $.data(tmpl, "tmpl", compile(tmpl.innerHTML));
                    }
                    viewsNs.templates[tmpl._name = tmpl._name || name || "_" + autoName++] = tmpl;
                }
                return tmpl;
            }
            // Return named compiled template
            return name
            ? "" + name !== name // not type string
                ? (name._name
                    ? name // already compiled
                    : $.template(null, name))
                : viewsNs.templates[name] ||
            // If not in map, treat as a selector. (If integrated with core, use quickExpr.exec)
                    $.template(null, htmlExpr.test(name) ? name : try$(name))
            : null;
        }
    });

    viewsNs.setDelimiters("{{", "}}");

    //=================
    // compile template
    //=================

    // Generate a reusable function that will serve to render a template against data
    // (Compile AST then build template function)

    function parsePath(all, comp, object, viewDataCtx, viewProperty, path, string, quot) {
        return object
        ? ((viewDataCtx
            ? viewProperty
                ? ("$view." + viewProperty)
                : object
            : ("$data." + object)
        ) + (path || ""))
        : string || (comp || "");
    }

    function compile(markup) {
        var newNode,
        loc = 0,
        stack = [],
        topNode = [],
        content = topNode,
        current = [, , topNode];

        function pushPreceedingContent(shift) {
            shift -= loc;
            if (shift) {
                content.push(markup.substr(loc, shift).replace(rNewLine, "\\n"));
            }
        }

        function parseTag(all, isBlock, tagName, equals, code, params, useEncode, encode, closeBlock, index) {
            // rTag    :    #    tagName          equals code        params         encode      closeBlock
            // /\{\{(?:(?:(\#)?(\w+(?=[\s\}!]))|(?:(\=)|(\*)))((?:[^\}]|\}(?!\}))*?)(!(\w*))?|(?:\/([\w\$\.\[\]]+)))\}\}/g;

            // Build abstract syntax tree: [ tagName, params, content, encode ]
            var named,
            hash = "",
            parenDepth = 0,
            quoted = FALSE, // boolean for string content in double qoutes
            aposed = FALSE; // or in single qoutes

            function parseParams(all, path, paren, comp, eq, comma, apos, quot, rightParen, space, index) {
                //      path          paren eq      comma   apos   quot  rtPrn  space
                // /(\$?[\w\.\[\]]+)(?:(\()|(===)|(\=))?|(\,\s*)|\\?(\')|\\?(\")|(\))|(\s+)/g

                return aposed
                // within single-quoted string
                ? (aposed = !apos, (aposed ? all : '"'))
                : quoted
                // within double-quoted string
                    ? (quoted = !quot, (quoted ? all : '"'))
                    : comp
                // comparison
                        ? (path.replace(rPath, parsePath) + comp)
                        : eq
                // named param
                            ? parenDepth ? "" : (named = TRUE, '\b' + path + ':')
                            : paren
                // function
                                ? (parenDepth++, path.replace(rPath, parsePath) + '(')
                                : rightParen
                // function
                                    ? (parenDepth--, ")")
                                    : path
                // path
                                        ? path.replace(rPath, parsePath)
                                        : comma
                                            ? ","
                                            : space
                                                ? (parenDepth
                                                    ? ""
                                                    : named
                                                        ? (named = FALSE, "\b")
                                                        : ","
                                                )
                                                : (aposed = apos, quoted = quot, '"');
            }

            tagName = tagName || equals;
            pushPreceedingContent(index);
            if (code) {
                if (viewsNs.allowCode) {
                    content.push(["*", params.replace(rUnescapeQuotes, "$1")]);
                }
            } else if (tagName) {
                if (tagName === "else") {
                    current = stack.pop();
                    content = current[2];
                    isBlock = TRUE;
                }
                params = (params
                ? (params + " ")
                    .replace(rParams, parseParams)
                    .replace(rBuildHash, function (all, keyValue, index) {
                        hash += keyValue + ",";
                        return "";
                    })
                : "");
                params = params.slice(0, -1);
                newNode = [
                tagName,
                useEncode ? encode || "none" : "",
                isBlock && [],
                "{" + hash + "_hash:'" + hash + "',_path:'" + params + "'}",
                params
            ];

                if (isBlock) {
                    stack.push(current);
                    current = newNode;
                }
                content.push(newNode);
            } else if (closeBlock) {
                current = stack.pop();
            }
            loc = index + all.length; // location marker - parsed up to here
            if (!current) {
                throw "Expected block tag";
            }
            content = current[2];
        }
        markup = markup.replace(rEscapeQuotes, "\\$1");
        markup.replace(rTag, parseTag);
        pushPreceedingContent(markup.length);
        return buildTmplFunction(topNode);
    }

    // Build javascript compiled template function, from AST
    function buildTmplFunction(nodes) {
        var ret, node, i,
        nested = [],
        l = nodes.length,
        code = "try{var views="
            + (jQuery ? "jQuery" : "JsViews")
            + '.views,tag=views.renderTag,enc=views.encode,html=views.encoders.html,$ctx=$view && $view.ctx,result=""+\n\n';

        for (i = 0; i < l; i++) {
            node = nodes[i];
            if (node[0] === "*") {
                code = code.slice(0, i ? -1 : -3) + ";" + node[1] + (i + 1 < l ? "result+=" : "");
            } else if ("" + node === node) { // type string
                code += '"' + node + '"+';
            } else {
                var tag = node[0],
                encode = node[1],
                content = node[2],
                obj = node[3],
                params = node[4],
                paramsOrEmptyString = params + '||"")+';

                if (content) {
                    nested.push(buildTmplFunction(content));
                }
                code += tag === "="
                ? (!encode || encode === "html"
                    ? "html(" + paramsOrEmptyString
                    : encode === "none"
                        ? ("(" + paramsOrEmptyString)
                        : ('enc("' + encode + '",' + paramsOrEmptyString)
                )
                : 'tag("' + tag + '",$view,"' + (encode || "") + '",'
                    + (content ? nested.length : '""') // For block tags, pass in the key (nested.length) to the nested content template
                    + "," + obj + (params ? "," : "") + params + ")+";
            }
        }
        ret = new Function("$data, $view", code.slice(0, -1) + ";return result;\n\n}catch(e){return views.err(e);}");
        ret.nested = nested;
        return ret;
    }

    //========================== Private helper functions, used by code above ==========================

    function replacerForHtml(ch) {
        // Original code from Mike Samuel <msamuel@google.com>
        return escapeMapForHtml[ch]
        // Intentional assignment that caches the result of encoding ch.
        || (escapeMapForHtml[ch] = "&#" + ch.charCodeAt(0) + ";");
    }

    function try$(selector) {
        // If selector is valid, return jQuery object, otherwise return (invalid) selector string
        try {
            return $(selector);
        } catch (e) { }
        return selector;
    }
})(window);
